﻿using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using System.Reflection;
using System.Collections;
using System.Data;

namespace AJAXServiceProxyGeneration
{
    public class ScriptSharpReflectorGenerator : ScriptSharpGenerator
    {
        public ScriptSharpReflectorGenerator(string serviceNamespace, string pocoNamespace, string assembly,string packageFilter, string excludes)
            : base(serviceNamespace, pocoNamespace, null, excludes)
        {
            this._assembly = assembly;
            this._packageFilter = packageFilter;
        }

        private string _assembly;
        private string _packageFilter;
        private IList<Type> _excludedInterfaces = new List<Type>(new Type[] {typeof(IDisposable)});

        /// <summary>
        /// Gets or sets the excluded interfaces.
        /// </summary>
        /// <value>The excluded interfaces.</value>
        public IList<Type> ExcludedInterfaces
        {
            get { return this._excludedInterfaces; }
            set { this._excludedInterfaces = value; }
        }

        /// <summary>
        /// Generate the code
        /// </summary>
        public override void Generate()
        {
            /*
             * We add some ordering to keep the order in which types
             * are generated are in the same order despite what is used
             * to build the assembly.
             */
            Assembly ass = Assembly.LoadFrom(this._assembly);
            List<Type> ls = new List<Type>(ass.GetTypes());
            ls.Sort(new Comparison<Type>(delegate(Type a, Type b)
                {
                    return string.Compare(a.FullName, b.FullName);
                }
            ));
            string[] packageFilters =this._packageFilter!=null?this._packageFilter.Split(',', ';','\n'):null;
            foreach (Type t in ls)
            {
                if (t.IsClass && !t.IsGenericType && !t.IsNested)
                {
                    if (packageFilters != null)
                    {
                        bool isInFilter = false;
                        foreach (string package in packageFilters)
                        {
                            if (t.FullName.StartsWith(package.Trim(), StringComparison.OrdinalIgnoreCase))
                            {
                                isInFilter = true;
                                break;
                            }
                        }
                        if(isInFilter)
                        {
                           this.ProcessType(t);
                        }
                    }
                    else
                    {
                        this.ProcessType(t);
                    }
                }
            }
        }
        
        private void ProcessType(Type t)
        {
            foreach (Type i in t.GetInterfaces())
            {
                if (!this.ExcludedInterfaces.Contains(i))
                {
                    CodeNamespace codeNamespace = new CodeNamespace("Ironic");
                    CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(t.Name);
                    codeNamespace.Types.Add(codeTypeDeclaration);

                    List<MethodInfo> mis = new List<MethodInfo>(i.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
                    mis.Sort(new Comparison<MethodInfo>(delegate(MethodInfo a, MethodInfo b)
                    {
                        return string.Compare(a.Name, b.Name);
                    }
                    ));
                    foreach (MethodInfo mi in mis)
                    {
                        this.GenerateMethod(codeNamespace, codeTypeDeclaration, mi);
                    }

                    this.ManipulateCodeDom(codeNamespace);
                }
            }
        }

        private void GenerateMethod(CodeNamespace codeNamespace, CodeTypeDeclaration codeTypeDec, MethodInfo method)
        {
            CodeMemberMethod code = new CodeMemberMethod();
            codeTypeDec.Members.Add(code);

            code.Name = method.Name;
            code.ReturnType = new CodeTypeReference(this.GetTypeName(codeNamespace,codeTypeDec, method.ReturnType));

            foreach (ParameterInfo pi in method.GetParameters())
            {
                code.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(this.GetTypeName(codeNamespace,codeTypeDec, pi.ParameterType)), pi.Name));
                this.GeneratePOCO(codeNamespace,  pi.ParameterType);
            }
            //Generate return types
            this.GeneratePOCO(codeNamespace, method.ReturnType);
            
        }
        private List<Type> pocoTypes = new List<Type>();
        private Dictionary<Type, string> typeNames = new Dictionary<Type, string>();

        private string GetTypeName(CodeNamespace ns, CodeTypeDeclaration dec, Type type)
        {
            return this.GetTypeName(ns, dec, type,false);
        }
        private string GetTypeName(CodeNamespace ns, CodeTypeDeclaration dec, Type type, bool isDeriving)
        {
            string name = this.GetCalcTypeName(ns, dec, type);

            if (type.Namespace != null &&
                (type.Namespace.Equals("System")
                || type.Namespace.Equals("System.Collections.Generic")
                || type.Namespace.Equals("System.Xml")))
                return name;
            bool caseInsensitiveFind = false;
            foreach(string s in this.typeNames.Values)
            {
                if (string.Equals(name, s, StringComparison.OrdinalIgnoreCase))
                {
                    caseInsensitiveFind = true;
                }
            }
            if (!this.typeNames.ContainsKey(type))
            {
                if (caseInsensitiveFind)
                {
                    //workout a different name
                    if (type.Namespace != null)
                    {
                        string[] split = type.Namespace.Split('.');
                        if (split.Length > 1)
                        {
                            name += split[split.Length - 1];
                        }
                        else
                        {
                            name += "1";
                        }
                    }
                }

                this.typeNames.Add(type, name);
            }
            
            return this.typeNames[type];
        }

        private string GetCalcTypeName(CodeNamespace ns, CodeTypeDeclaration dec, Type type)
        {
            if (type.Namespace != null &&
                (type.Namespace.Equals("System")
                || type.Namespace.Equals("System.Xml")))
                return type.FullName;

            if (type.IsGenericType)
            {
                if (type.GetGenericArguments().Length == 1)
                {
                    return this.GetTypeName(ns, dec, type.GetGenericArguments()[0]) + "[]";
                }
                if (type.GetGenericArguments().Length > 1)
                {
                    return typeof(object).FullName + "[]";
                }
            }
            if (type.IsArray)
            {
                return this.GetTypeName(ns, dec, type.GetElementType()) + "[]";
            }

            this.GeneratePOCO(ns, type);

            return type.Name;
        }

        private void GeneratePOCO(CodeNamespace codeNS, Type type)
        {
            this.GeneratePOCO(codeNS, type, false);
        }

        private void GeneratePOCO(CodeNamespace codeNS, Type type, bool isDeriving)
        {
            if (!type.IsEnum)
            {
                if (type.IsPrimitive) return;
                if (type.IsValueType) return;
                if (type.IsArray) return;
                if (type.IsGenericType) return;
                if (type.Namespace == "System") return;
                if (type.Namespace == "System.Xml") return;
            }
            
            if (!pocoTypes.Contains(type))
            {
                pocoTypes.Add(type);

                CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(this.GetTypeName(codeNS,null,type));
                
                if (type.IsEnum)
                {
                    codeTypeDeclaration.IsEnum = true;
                    foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.Static))
                    {
                        CodeMemberField field = new CodeMemberField(typeof(int), fi.Name);
                        //What about values
                        codeTypeDeclaration.Members.Add(field);
                    }
                    
                }
                else
                {
                    codeTypeDeclaration.IsClass = true;
                    foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                    {
                        //Check for base types
                        if (pi.GetGetMethod().GetBaseDefinition() != null && pi.GetGetMethod().GetBaseDefinition().DeclaringType.Equals(type))
                        {
                            this.GenerateFieldFromProperty(codeNS, codeTypeDeclaration, pi);
                        }
                    }

                    if (type.BaseType != null && !type.BaseType.Equals(typeof(Object)))
                    {
                        if (type.BaseType.IsGenericType)
                        {
                            CodeTypeReference baseType = new CodeTypeReference(this.GenerateGenericClass(codeNS, type.BaseType));
                            codeTypeDeclaration.BaseTypes.Add(baseType);
                        }
                        else
                        {
                            this.GeneratePOCO(codeNS, type.BaseType,true);
                            codeTypeDeclaration.BaseTypes.Add(new CodeTypeReference(type.BaseType.Name));
                        }

                    }
                }
                codeNS.Types.Add(codeTypeDeclaration);
                if( !isDeriving)
                    this.GenerateDerived(codeNS, type);
            }
        }

        private string GenerateGenericClass(CodeNamespace codeNS, Type type)
        {
            /*
             * Some .NET Classes are not scheme generated and thus may have some generics
             * Generics aren't supported in Script# or and is interpretted differently
             * in WSDL; this implementation classes are generated
             */
            string name = type.Name.Substring(0, type.Name.Length - 2) + "Of" + type.GetGenericArguments()[0].Name;
                
            if (!this.pocoTypes.Contains(type))
            {
                CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(name);
                codeTypeDeclaration.IsClass = true;
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                {
                    this.GenerateFieldFromProperty(codeNS, codeTypeDeclaration, pi);
                }
                if (type.BaseType.IsGenericType)
                {
                    CodeTypeReference baseType = new CodeTypeReference(this.GenerateGenericClass(codeNS, type.BaseType));
                    codeTypeDeclaration.BaseTypes.Add(baseType);
                }
                codeNS.Types.Add(codeTypeDeclaration);

                this.pocoTypes.Add(type);
            }
            return name;
            
        }

        private void GenerateDerived(CodeNamespace codeNS, Type type)
        {
            /* Find all derived types from currently loaded assemblies
             * 
             * I don't know the best possible way here?
             * 
             * Need some cleverness here to make this efficient!
             */
            
            Type[] types = type.Assembly.GetTypes();
            foreach (Type t in types)
            {
                if (t.BaseType != null
                    && t.BaseType.Equals(type)
                    && t.IsPublic)
                {
                    this.GeneratePOCO(codeNS, t);
                }
            }
            
        }

        private void GenerateFieldFromProperty(CodeNamespace codeNS,CodeTypeDeclaration codeTypeDec, PropertyInfo property)
        {
            CodeMemberField code = new CodeMemberField();
            code.Name = property.Name;
            code.Type = new CodeTypeReference(this.GetTypeName(codeNS,codeTypeDec, property.PropertyType));
            if (property.PropertyType.IsClass)
            {
                this.GeneratePOCO(codeNS, property.PropertyType);
            }
            codeTypeDec.Members.Add(code);
        }
    }
}
